package com.yuma.tingshu.account.listener;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.rabbitmq.client.Channel;
import com.yuma.tingshu.account.mapper.RechargeInfoMapper;
import com.yuma.tingshu.account.mapper.UserAccountDetailMapper;
import com.yuma.tingshu.account.mapper.UserAccountMapper;
import com.yuma.tingshu.common.constant.RabbitConstant;
import com.yuma.tingshu.common.constant.RedisConstant;
import com.yuma.tingshu.common.constant.SystemConstant;
import com.yuma.tingshu.common.execption.YumaException;
import com.yuma.tingshu.common.result.ResultCodeEnum;
import com.yuma.tingshu.model.account.RechargeInfo;
import com.yuma.tingshu.model.account.UserAccountDetail;
import com.yuma.tingshu.vo.account.AccountLockResultVo;
import com.yuma.tingshu.vo.user.UserPaidRecordVo;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Component
public class AccountListener {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private UserAccountMapper userAccountMapper;
    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;
    @Autowired
    private RechargeInfoMapper rechargeInfoMapper;

    // 充值成功
    @Transactional
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(RabbitConstant.RECHARGE_PAID_QUEUE),
            exchange = @Exchange(value = RabbitConstant.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC, ignoreDeclarationExceptions = "true"),
            key = RabbitConstant.RECHARGE_PAID_RK
    ))
    public void recharge(String orderNo, Channel channel, Message message) throws IOException {
        // 1.判空
        if (StringUtils.isBlank(orderNo)) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        // 2.幂等性
        String idemKey = RedisConstant.RECHARGE_PAID_IDEM_PREFIX + orderNo;
        if (!redisTemplate.opsForValue().setIfAbsent(idemKey, "1", RedisConstant.IDEM_TIMEOUT, TimeUnit.SECONDS)) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        // 3.根据orderNo从recharge_info表中查询充值订单，并判空
        RechargeInfo rechargeInfo = rechargeInfoMapper.selectOne(new LambdaQueryWrapper<RechargeInfo>()
                .eq(RechargeInfo::getOrderNo, orderNo));
        if (rechargeInfo == null) {
            // TODO: 发送消息给账户，把钱退款回去
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
        }
        try {
            // 4.更新recharge_info订单状态 0901 -> 0902
            rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_PAID);
            if (this.rechargeInfoMapper.updateById(rechargeInfo) != 1) {
                throw new YumaException(ResultCodeEnum.DATA_ERROR);
            }
            // 5.给user_account表加钱，如果成功
            Long userId = rechargeInfo.getUserId();
            BigDecimal amount = rechargeInfo.getRechargeAmount();
            if (this.userAccountMapper.plus(userId, amount) == 1) {
                // 6.写入user_account_detail表
                UserAccountDetail userAccountDetail = new UserAccountDetail();
                userAccountDetail.setUserId(userId);
                userAccountDetail.setAmount(amount);
                userAccountDetail.setTitle("账户充值");
                userAccountDetail.setOrderNo(orderNo);
                userAccountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT);
                this.userAccountDetailMapper.insert(userAccountDetail);
            } else {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            }
        } catch (YumaException e) {
            if (!message.getMessageProperties().getRedelivered()) {
                redisTemplate.delete(idemKey);
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
            } else {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            }
        }
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    // 账户扣减
    @Transactional
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(RabbitConstant.ACCOUNT_MINUS_QUEUE),
            exchange = @Exchange(value = RabbitConstant.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC, ignoreDeclarationExceptions = "true"),
            key = RabbitConstant.ACCOUNT_MINUS_RK
    ))
    public void minus(String orderNo, Channel channel, Message message) throws IOException {
        // 1.判空
        if (StringUtils.isBlank(orderNo)) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        // 2.幂等性setIfAbsent
        String idemKey = RedisConstant.ACCOUNT_MINUS_IDEM_PREFIX + orderNo;
        // 尝试设置键值：使用 setIfAbsent 在 Redis 中设置键，若已存在则返回 false
        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent(idemKey, "", RedisConstant.IDEM_TIMEOUT, TimeUnit.SECONDS);
        if (!flag) {
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        // 3.从Redis中获取锁定账户结果 AccountLockResultVo
        String lockKey = RedisConstant.ACCOUNT_LOCK_INFO_PREFIX + orderNo;
        AccountLockResultVo lockResultVo = (AccountLockResultVo) this.redisTemplate.opsForValue().get(lockKey);
        if (ObjectUtils.isEmpty(lockResultVo)) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        try {
            // 在此环节中操作数据库，如果发生了异常，删除幂等性key（以便失败重试）
            // 4.扣减余额 user_account -> update
            int minusRow = userAccountMapper.minus(lockResultVo.getUserId(), lockResultVo.getAmount());
            if (minusRow == 1) {
                // 5.写入 user_account_detail表
                UserAccountDetail userAccountDetail = new UserAccountDetail();
                userAccountDetail.setUserId(lockResultVo.getUserId());
                userAccountDetail.setTitle("账户余额扣减");
                userAccountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_MINUS);
                userAccountDetail.setAmount(lockResultVo.getAmount());
                userAccountDetail.setOrderNo(orderNo);
                this.userAccountDetailMapper.insert(userAccountDetail);
                // 成功后锁定结果后面不会使用，删除锁定信息的缓存(AccountLockResultVo账户锁定对象checkLock->minus)
                this.redisTemplate.delete(lockKey);

                // 6.发送rabbit Order队列中
                this.rabbitTemplate.convertAndSend(RabbitConstant.ORDER_EXCHANGE, RabbitConstant.ORDER_PAID_RK, orderNo);
            }
        } catch (Exception e) {
            // 一旦出现异常，删除幂等性的key，以便于进行重试
            if (!message.getMessageProperties().getRedelivered()) {
                this.redisTemplate.delete(idemKey);
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
            } else {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            }
            throw new RuntimeException(e);
        }
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    // 失败解锁余额
    @Transactional
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(RabbitConstant.ACCOUNT_UNLOCK_QUEUE),
            exchange = @Exchange(value = RabbitConstant.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC, ignoreDeclarationExceptions = "true"),
            key = RabbitConstant.ACCOUNT_UNLOCK_RK
    ))
    public void unlock(String orderNo, Channel channel, Message message) throws IOException {
        if (StringUtils.isBlank(orderNo)) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        // 保证幂等性
        String idemKey = RedisConstant.ACCOUNT_UNLOCK_IDEM_PREFIX + orderNo;
        Boolean flag = this.redisTemplate.opsForValue().setIfAbsent(idemKey, "", RedisConstant.IDEM_TIMEOUT, TimeUnit.SECONDS);
        if (!flag) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        // 查询锁定信息的缓存
        String lockKey = RedisConstant.ACCOUNT_LOCK_INFO_PREFIX + orderNo;
        AccountLockResultVo resultVo = (AccountLockResultVo) this.redisTemplate.opsForValue().get(lockKey);
        if (resultVo == null) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        try {
            int unlockRow = userAccountMapper.unlock(resultVo.getUserId(), resultVo.getAmount());
            // 解锁余额
            if (unlockRow == 1) {
                // 记录账户变化明细
                UserAccountDetail userAccountDetail = new UserAccountDetail();
                userAccountDetail.setUserId(resultVo.getUserId());
                userAccountDetail.setTitle("账户余额解锁");
                userAccountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK);
                userAccountDetail.setAmount(resultVo.getAmount());
                userAccountDetail.setOrderNo(orderNo);
                this.userAccountDetailMapper.insert(userAccountDetail);
                // 删除锁定信息的缓存
                this.redisTemplate.delete(lockKey);
            }
        } catch (Exception e) {
            // 一旦出现异常，删除幂等性的key，以便于进行重试
            if (!message.getMessageProperties().getRedelivered()) {
                this.redisTemplate.delete(idemKey);
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
            } else {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            }
            throw new RuntimeException(e);
        }
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}
